﻿//OrderLogSerializer.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Storages
{
	using System;
	using System.Collections.Generic;
	using System.IO;
	using System.Linq;

	using Ecng.Collections;
	using Ecng.Common;
	using Ecng.Serialization;

	using StockSharp.BusinessEntities;

	class OrderLogMetaInfo : MetaInfo<OrderLogMetaInfo>
	{
		public OrderLogMetaInfo(DateTime date)
			: base(date)
		{
			FirstOrderId = -1;
			Portfolios = new List<string>();
		}

		public long FirstOrderId { get; set; }
		public long LastOrderId { get; set; }

		public long FirstTradeId { get; set; }
		public long LastTradeId { get; set; }

		public long FirstTransactionId { get; set; }
		public long LastTransactionId { get; set; }

		public IList<string> Portfolios { get; private set; }

		public override void Write(Stream s)
		{
			base.Write(s);

			s.Write(FirstOrderId);
			s.Write(FirstTradeId);
			s.Write(LastOrderId);
			s.Write(LastTradeId);
			s.Write(FirstPrice);
			s.Write(LastPrice);

			if (Version < MarketDataVersions.Version34)
				return;

			s.Write(FirstTransactionId);
			s.Write(LastTransactionId);

			if (Version < MarketDataVersions.Version40)
				return;

			s.Write(Portfolios.Count);

			foreach (var portfolio in Portfolios)
				s.Write(portfolio);

			if (Version < MarketDataVersions.Version43)
				return;

			s.Write(FirstNonSystemPrice);
			s.Write(PrevNonSystemPrice);
		}

		public override void Read(Stream s)
		{
			base.Read(s);

			FirstOrderId = s.Read<long>();
			FirstTradeId = s.Read<long>();
			LastOrderId = s.Read<long>();
			LastTradeId = s.Read<long>();
			FirstPrice = s.Read<decimal>();
			LastPrice = s.Read<decimal>();

			if (Version < MarketDataVersions.Version34)
				return;

			FirstTransactionId = s.Read<long>();
			LastTransactionId = s.Read<long>();

			if (Version < MarketDataVersions.Version40)
				return;

			var count = s.Read<int>();

			for (var i = 0; i < count; i++)
				Portfolios.Add(s.Read<string>());

			if (Version < MarketDataVersions.Version43)
				return;

			FirstNonSystemPrice = s.Read<decimal>();
			PrevNonSystemPrice = s.Read<decimal>();
		}

		protected override void CopyFrom(OrderLogMetaInfo src)
		{
			base.CopyFrom(src);

			FirstOrderId = src.FirstOrderId;
			FirstTradeId = src.FirstTradeId;
			LastOrderId = src.LastOrderId;
			LastTradeId = src.LastTradeId;

			FirstPrice = src.FirstPrice;
			LastPrice = src.LastPrice;

			FirstTransactionId = src.FirstTransactionId;
			LastTransactionId = src.LastTransactionId;

			Portfolios.Clear();
			Portfolios.AddRange(src.Portfolios);

			FirstNonSystemPrice = src.FirstNonSystemPrice;
			PrevNonSystemPrice = src.PrevNonSystemPrice;
		}
	}

	class OrderLogSerializer : MarketDataSerializer<OrderLogItem, OrderLogMetaInfo>
	{
		public OrderLogSerializer(Security security)
			: base(security, 200)
		{
		}

		protected override void OnSave(List<bool> bits, IEnumerable<OrderLogItem> items, OrderLogMetaInfo metaInfo)
		{
			if (metaInfo.IsEmpty() && !items.IsEmpty())
			{
				var item = items.First();

				metaInfo.FirstOrderId = metaInfo.LastOrderId = item.Order.Id;
				metaInfo.FirstTransactionId = metaInfo.LastTransactionId = item.Order.TransactionId;
			}

			bits.SerializeInt(items.Count());

			foreach (var item in items)
			{
				var order = item.Order;
				var trade = item.Trade;

				if (order.Id <= 0)
					throw new ArgumentOutOfRangeException("items", order.Id, "Неправильный номер заявки.");

				// нулевая цена возможна, если идет "рыночная" продажа по инструменту без планок
				if (order.Price < 0)
					throw new ArgumentOutOfRangeException("items", order.Price, "Неправильная цена заявки {0}.".Put(order.Id));

				if (order.Volume <= 0)
					throw new ArgumentOutOfRangeException("items", order.Volume, "Неправильный объем заявки {0}.".Put(order.Id));

				if (trade != null)
				{
					if (trade.Id <= 0)
						throw new ArgumentOutOfRangeException("items", trade.Id, "Неправильный номер сделки заявки {0}.".Put(order.Id));

					if (trade.Price <= 0)
						throw new ArgumentOutOfRangeException("items", trade.Price, "Неправильная цена сделки {0} заявки {1}.".Put(trade.Id, order.Id));

					if (order.IsSystem != trade.IsSystem)
						throw new ArgumentException("items", "Признак системности у сделки {0} и заявки {1} не совпадают.".Put(trade.Id, order.Id));
				}

				metaInfo.LastOrderId = bits.SerializeId(order.Id, metaInfo.LastOrderId);

				bits.SerializePriceEx(order.Price, metaInfo, Security);
				bits.SerializeVolume(order.Volume);

				bits.Add(order.Direction == OrderDirections.Buy);

				metaInfo.LastTime = bits.SerializeTime(order.Time, metaInfo.LastTime, "заявки");

				if (trade != null)
				{
					bits.Add(true);

					if (metaInfo.FirstTradeId == 0)
					{
						metaInfo.FirstTradeId = metaInfo.LastTradeId = trade.Id;
					}

					metaInfo.LastTradeId = bits.SerializeId(trade.Id, metaInfo.LastTradeId);
					
					bits.SerializePriceEx(trade.Price, metaInfo, Security);
				}
				else
				{
					bits.Add(false);
					bits.Add(order.State == OrderStates.Active);
				}

				if (metaInfo.Version < MarketDataVersions.Version31)
					continue;

				var status = order.Status;

				if (status == null)
					bits.Add(false);
				else
				{
					bits.Add(true);
					bits.SerializeInt((int)status);
				}

				if (metaInfo.Version < MarketDataVersions.Version33)
					continue;

				bits.SerializeInt((int)order.ExecutionCondition);
				bits.Add(order.IsSystem);

				if (metaInfo.Version < MarketDataVersions.Version34)
					continue;

				metaInfo.LastTransactionId = bits.SerializeId(order.TransactionId, metaInfo.LastTransactionId);

				if (metaInfo.Version < MarketDataVersions.Version40)
					continue;

				bits.SerializeLong(item.Latency.Ticks);

				var portfolio = order.Portfolio;
				var isEmptyPf = portfolio == null || portfolio == Portfolio.AnonymousPortfolio;

				bits.Add(!isEmptyPf);

				if (isEmptyPf)
					continue;

				metaInfo.Portfolios.TryAdd(portfolio.Name);
				bits.SerializeInt(metaInfo.Portfolios.IndexOf(portfolio.Name));
			}
		}

		public override OrderLogItem MoveNext(MarketDataEnumerator<OrderLogItem, OrderLogMetaInfo> enumerator)
		{
			var reader = enumerator.Reader;
			var metaInfo = enumerator.MetaInfo;

			metaInfo.FirstOrderId += reader.DeserializeLong();
			var price = reader.DeserializePriceEx(metaInfo);

			var volume = reader.DeserializeVolume();

			var orderDirection = reader.Read() ? OrderDirections.Buy : OrderDirections.Sell;

			metaInfo.FirstTime = reader.DeserializeTime(enumerator.Date, metaInfo.FirstTime);

			var order = new Order
			{
				Security = Security,
				Id = metaInfo.FirstOrderId,
				Volume = volume,
				Direction = orderDirection,
				Time = metaInfo.FirstTime,
				Price = price,
			};

			Trade trade;

			if (reader.Read())
			{
				metaInfo.FirstTradeId += reader.DeserializeLong();
				price = reader.DeserializePriceEx(metaInfo);

				trade = new Trade
				{
					Security = Security,
					Id = metaInfo.FirstTradeId,
					Time = metaInfo.FirstTime,
					Volume = volume,
					Price = price,
				};

				order.State = OrderStates.Done;
			}
			else
			{
				trade = null;
				order.State = reader.Read() ? OrderStates.Active : OrderStates.Done;
			}

			if (metaInfo.Version >= MarketDataVersions.Version31)
			{
				if (reader.Read())
				{
					var status = reader.DeserializeInt();
					order.Status = (OrderStatus?)status;

					if (status.HasBits(0x01))
						order.ExecutionCondition = OrderExecutionConditions.PutInQueue;
					else if (status.HasBits(0x02))
						order.ExecutionCondition = OrderExecutionConditions.CancelBalance;
				}

				// Лучше ExecCond писать отдельным полем так как возможно только Плаза пишет это в статус
				if (metaInfo.Version >= MarketDataVersions.Version33)
				{
					order.ExecutionCondition = (OrderExecutionConditions)reader.DeserializeInt();
					order.IsSystem = reader.Read();

					if (metaInfo.Version >= MarketDataVersions.Version34)
					{
						metaInfo.FirstTransactionId += reader.DeserializeLong();
						order.TransactionId = metaInfo.FirstTransactionId;
					}
				}
				else
				{
					if (order.Status != null)
						order.IsSystem = !((int)order.Status).HasBits(0x04);
				}
			}

			var item = new OrderLogItem
			{
				Order = order,
				Trade = trade,
			};

			if (trade != null)
				trade.IsSystem = order.IsSystem;

			if (metaInfo.Version >= MarketDataVersions.Version40)
			{
				item.Latency = reader.ReadLong().To<TimeSpan>();

				if (reader.Read())
				{
					order.Portfolio = new Portfolio { Name = metaInfo.Portfolios[reader.ReadInt()] };
				}
			}

			//if (order.Portfolio == null)
			//	order.Portfolio = Portfolio.AnonymousPortfolio;

			return item;
		}
	}
}